package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

// BaliExecutor todo
type BaliExecutor struct {
	de       *Derivator
	Target   string // os
	Arch     string
	Out      string
	Cwd      string
	Dist     string
	Zip      bool
	Mkstgz   bool
	Environ  []string
	BM       BaliMetadata
	Binaries []string
}

func resolveBuildID(cwd string) string {
	cmd := exec.Command("git", "rev-parse", "HEAD")
	cmd.Dir = cwd
	if out, err := cmd.CombinedOutput(); err == nil {
		commitid := strings.TrimSpace(string(out))
		DbgPrint("BUILD_COMMIT: '%s'", commitid)
		return commitid
	}
	return "None"
}

func resolveGoVersion() string {
	cmd := exec.Command("go", "version")
	if out, err := cmd.CombinedOutput(); err == nil {
		goversion := strings.TrimPrefix(strings.TrimSpace(string(out)), "go version ")
		DbgPrint("Go Version: '%s'", goversion)
		return goversion
	}
	return "None"
}

func resolveDistSupport(target, arch string) bool {
	cmd := exec.Command("go", "tool", "dist", "list")
	if out, err := cmd.CombinedOutput(); err == nil {
		str := StrCat(target, "/", arch)
		if strings.Contains(string(out), str) {
			return true
		}
	}
	return false
}

// Initialize todo
func (be *BaliExecutor) Initialize() error {
	bali := filepath.Join(be.Cwd, "bali.json")
	if err := LoadMetadata(bali, &be.BM); err != nil {
		return err
	}
	if len(be.BM.Version) == 0 {
		be.BM.Version = "0.0.1"
	}
	if be.BM.Dirs == nil && be.BM.DirFallback != nil {
		fmt.Fprintf(os.Stderr, "'\x1b[33mDirs\x1b[0m' will be replaced by '\x1b[32mdirs\x1b[0m'\n")
		be.BM.Dirs = be.BM.DirFallback
	}
	if be.BM.Files == nil && be.BM.FileFallback != nil {
		fmt.Fprintf(os.Stderr, "'\x1b[33minstall\x1b[0m' will be replaced by '\x1b[32mfiles\x1b[0m'\n")
		be.BM.Files = be.BM.FileFallback
	}
	be.de = NewDerivator()
	// Respect environmental variable settings
	if len(be.Target) == 0 {
		if be.Target = os.Getenv("GOOS"); len(be.Target) == 0 {
			be.Target = runtime.GOOS
		}
	}
	if len(be.Arch) == 0 {
		if be.Arch = os.Getenv("GOARCH"); len(be.Arch) == 0 {
			be.Arch = runtime.GOARCH
		}
	}
	if !resolveDistSupport(be.Target, be.Arch) {
		return ErrorCat("unsupported GOOS/GOARCH pair ", be.Target, "/", be.Arch)
	}
	_ = be.de.Append("BUILD_COMMIT", resolveBuildID(be.Cwd))
	_ = be.de.Append("BUILD_GOVERSION", resolveGoVersion())
	_ = be.de.Append("BUILD_VERSION", "0.0.1")
	t := time.Now()
	_ = be.de.Append("BUILD_TIME", t.Format(time.RFC3339))
	osenv := os.Environ()
	be.Environ = make([]string, 0, len(osenv)+3)
	for _, e := range osenv {
		// remove old env
		if !strings.HasPrefix(e, "GOOS=") && !strings.HasPrefix(e, "GOARCH=") {
			be.Environ = append(be.Environ, e)
		}
	}
	be.Environ = append(be.Environ, StrCat("GOOS=", be.Target))
	be.Environ = append(be.Environ, StrCat("GOARCH=", be.Arch))
	return nil
}

// UpdateNow todo
func (be *BaliExecutor) UpdateNow(version string) {
	_ = be.de.Append("BUILD_VERSION", version)
	t := time.Now()
	_ = be.de.Append("BUILD_TIME", t.Format(time.RFC3339))
}

// ExpandEnv todo
func (be *BaliExecutor) ExpandEnv(s string) string {
	return be.de.ExpandEnv(s)
}

// BinaryName todo
func (be *BaliExecutor) BinaryName(dir, name string) string {
	var suffix string
	if be.Target == "windows" {
		suffix = ".exe"
	}
	if len(name) == 0 {
		return StrCat(filepath.Base(dir), suffix)
	}
	return StrCat(name, suffix)
}

// PathInArchive todo
func (be *BaliExecutor) PathInArchive(destination string) string {
	if be.Mkstgz {
		return destination
	}
	return filepath.Join(StrCat(be.BM.Name, "-", be.Target, "-", be.Arch, "-", be.BM.Version), destination)
}

// CompileAll todo
func (be *BaliExecutor) CompileAll() error {
	var err error
	for _, s := range be.BM.Dirs {
		dir := filepath.Join(be.Cwd, s)
		if err2 := be.Compile(dir); err2 != nil {
			fmt.Fprintf(os.Stderr, "bali compile: \x1b[31m%s\x1b[0m\n", err2)
			err = err2
		}
	}
	return err
}

// FileInstall todo
func (be *BaliExecutor) FileInstall() error {
	return be.BM.FileInstall(be.Cwd, be.Out)
}

// CompressZip todo
func (be *BaliExecutor) CompressZip() error {
	if !PathDirExists(be.Dist) {
		if err := os.MkdirAll(be.Dist, 0755); err != nil {
			return err
		}
	}
	outfile := filepath.Join(be.Dist, StrCat(be.BM.Name, "-", be.Target, "-", be.Arch, "-", be.BM.Version, ".zip"))
	zc, err := NewZipCompressor(outfile)
	if err != nil {
		return err
	}
	defer zc.Close()
	for _, s := range be.Binaries {
		rel, err := filepath.Rel(be.Out, s)
		if err != nil {
			return err
		}
		fmt.Fprintf(os.Stderr, "compress target \x1b[32m%s\x1b[0m\n", rel)
		if err := zc.AddFile(s, be.PathInArchive(rel)); err != nil {
			return err
		}
	}
	for _, p := range be.BM.Files {
		file := filepath.Join(be.Cwd, p.Path)
		rel := filepath.Join(p.Destination, filepath.Base(file))
		fmt.Fprintf(os.Stderr, "compress profile \x1b[32m%s\x1b[0m\n", rel)
		if err := zc.AddFile(file, be.PathInArchive(rel)); err != nil {
			return err
		}
	}
	fmt.Fprintf(os.Stderr, "\x1b[32mcreate %s success\x1b[0m\n", outfile)
	return nil
}

// CompressTarGz todo
func (be *BaliExecutor) CompressTarGz() error {
	if !PathDirExists(be.Dist) {
		if err := os.MkdirAll(be.Dist, 0755); err != nil {
			return err
		}
	}
	outfile := filepath.Join(be.Dist, StrCat(be.BM.Name, "-", be.Target, "-", be.Arch, "-", be.BM.Version, ".tar.gz"))
	c, err := NewCompressor(outfile)
	if err != nil {
		return err
	}
	defer c.Close()
	for _, s := range be.Binaries {
		rel, err := filepath.Rel(be.Out, s)
		if err != nil {
			return err
		}
		fmt.Fprintf(os.Stderr, "compress target \x1b[32m%s\x1b[0m\n", rel)
		if err := c.tar.AddFile(s, be.PathInArchive(rel)); err != nil {
			return err
		}
	}
	for _, p := range be.BM.Files {
		file := filepath.Join(be.Cwd, p.Path)
		rel := filepath.Join(p.Destination, filepath.Base(file))
		fmt.Fprintf(os.Stderr, "compress profile \x1b[32m%s\x1b[0m\n", rel)
		if err := c.tar.AddFile(file, be.PathInArchive(rel)); err != nil {
			return err
		}
	}
	fmt.Fprintf(os.Stderr, "\x1b[32mcreate %s success\x1b[0m\n", outfile)
	return nil
}

// PackageCompress todo
func (be *BaliExecutor) PackageCompress() error {
	if !PathDirExists(be.Dist) {
		if err := os.MkdirAll(be.Dist, 0755); err != nil {
			return err
		}
	}
	outfilename := StrCat(be.BM.Name, "-", be.Target, "-", be.Arch, "-", be.BM.Version, ".sh")
	outfile := filepath.Join(be.Dist, outfilename)
	postscript := filepath.Join(os.TempDir(), "post_install.sh")
	uw, err := NewUtilizeWriter(filepath.Join(postscript))
	if err != nil {
		return err
	}
	if err := uw.WriteBase(); err != nil {
		uw.Close()
		return err
	}
	c, err := NewCompressorSTGZ(outfile)
	if err != nil {
		_ = uw.Close()
		return err
	}
	defer c.Close()
	for _, s := range be.Binaries {
		rel, err := filepath.Rel(be.Out, s)
		if err != nil {
			_ = uw.Close()
			return err
		}
		fmt.Fprintf(os.Stderr, "compress target \x1b[32m%s\x1b[0m\n", rel)
		nameInArchive := StrCat(be.PathInArchive(rel), ".new")
		if err := c.tar.AddFile(s, nameInArchive); err != nil {
			_ = uw.Close()
			return err
		}
		_ = uw.AddTarget(nameInArchive)
	}
	for _, p := range be.BM.Files {
		file := filepath.Join(be.Cwd, p.Path)
		rel := filepath.Join(p.Destination, filepath.Base(file))
		fmt.Fprintf(os.Stderr, "compress profile \x1b[32m%s\x1b[0m\n", rel)
		nameInArchive := StrCat(be.PathInArchive(rel), ".template")
		if err := c.tar.AddFile(file, nameInArchive); err != nil {
			_ = uw.Close()
			return err
		}
		_ = uw.AddProfile(nameInArchive)
	}
	_ = uw.Close()
	if err := c.tar.AddFile(postscript, "post_install.sh"); err != nil {
		return err
	}
	fmt.Fprintf(os.Stderr, "create \x1b[32m%s\x1b[0m done\n", outfile)
	fmt.Fprintf(os.Stderr, "Your can run '\x1b[32m./%s --prefix=/path/to/%s\x1b[0m' to install %s\n", outfilename, be.BM.Name, be.BM.Name)
	return nil
}
